#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <time.h>
#include <gmp.h>
#include <sys/time.h>

gmp_randstate_t state;

double drand();
void *add_pontoCirculo(void *);

float pontosCirculo1 = 0, pontosCirculo2 = 0, pontosCirculo3 = 0;

int main() {

    int i;
    float pi;
    long int amostras = 10000000;
    pthread_t thread1, thread2, thread3; 

    // Declara variaveis para fazer o calculo do speedup do programa.
    struct timeval antes, depois;

    // Pega a hora atual do sistema (desde o Epoch), para saber quando o programa comecou a executar
    gettimeofday(&antes, 0);

    // Declara a variavel para manipular os arquivos que guardarao o valor calculado de PI a cada iteracao
    FILE *arq;

    /* Inicializa semente da função randômica */
    gmp_randinit_default (state);  

    /* Para cada amostra, criar um ponto em (px, py) dentro do quadrado e
       verificar se está dentro do círculo.*/
    
    for(i = 0; i < amostras/3; i++) {
        pthread_create(&thread1, NULL, add_pontoCirculo, (void *)1);
        pthread_create(&thread2, NULL, add_pontoCirculo, (void *)2);
	    pthread_create(&thread3, NULL, add_pontoCirculo, (void *)3);
	 
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        pthread_join(thread3, NULL);      
    }
       
    /* PI = 4 * Área_círculo/Área_quadrado => Área_círculo/Área_quadrado = 
       Pontos_no_círculo/Total_de_pontos. */
    pi = (4*(pontosCirculo1+pontosCirculo2+pontosCirculo3))/amostras;

    // Abre o arquivo que vai guardar o resultado de PI na iteracao atual
    arq = fopen("MonteCarloPararelo.txt", "w");
    
    if(arq == NULL) {
        printf("Problema ao abrir o arquivo");
        return 1;
    }

    // Imprime o valor calculado de PI no arquivo
    fprintf(arq, "%.30f", pi);

    // Fecha o arquivo desta iteracao
    fclose(arq);

    // Pega a hora atual do sistema (desde o Epoch), para saber quando o programa terminou de executar
    gettimeofday(&depois, 0);

    // Declara a variavel que guardara o valor do speedup do programa
    float speedup;

    // Faz a subtracao entre o horario que o programa terminou e o horario que o programa comecou, para saber quanto tempo ele levou para ser executado
    speedup = (depois.tv_sec + depois.tv_usec/1000000.0) - (antes.tv_sec + antes.tv_usec/1000000.0);
    
    // Abre o arquivo que vai guardar os speedups
    arq = fopen("Speedups.txt", "a+");
    if(arq == NULL){
	printf("Problema ao abrir o arquivo");
    	return 1;
    }

    // Imprime o speedup do programa no arquivo Speedups.txt
    fprintf(arq,"Monte Carlo paralelo: %.10f segundos.\n", speedup);
    
    // Fecha o arquivo de speedup
    fclose(arq);

    return 0;

}

double drand() {

    /* Random integer da GMP */
 
    mpz_t rand_number, rand_max;
    double r;
    
    mpz_init(rand_number);
    mpz_init(rand_max);
    mpz_set_ui(rand_max, 1000000);

    mpz_urandomm(rand_number, state, rand_max);

    r = mpz_get_d(rand_number)/1000000;

    mpz_clear(rand_number);
    mpz_clear(rand_max);
    
    return r*2;
}

void *add_pontoCirculo(void * param) {

    double px, py;

    px = drand();
    py = drand();
                         
    /* Verificar se a distância do ponto ao centro do círculo
       é menor ou igual a 1. Em caso afirmativo, o ponto pertence 
       ao círculo.*/
    if(sqrt( pow(px - 1, 2) + pow(py - 1, 2) ) <= 1) {
        /* Itera pontosCirculos */
        if((int)param == 1)
            pontosCirculo1++;
        else if ((int)param == 2)
            pontosCirculo2++;
        else if ((int)param == 3)
            pontosCirculo3++;
    }

    return NULL;  
}
